package com.yn.xgame;

import com.google.protobuf.GeneratedMessage;
import com.yn.xgame.NettyProtobufHandler;
import com.yn.xgame.message.Mq2ClientMessage;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.net.InetSocketAddress;

/**
 * NettyServer
 * Created by wangboo on 2017/7/11.
 */
public class NettyServer {

    /**
     * 报文格式 包体长度(2bytes) 是否zip压缩(1bit) 序列号(7bit) 包体(left)
     * lengthFieldOffset  = 0 (= the length of HDR1)
     * lengthFieldLength   = 2
     * lengthAdjustment    = 1 (= the length of HDR2)
     * initialBytesToStrip = 2 (= the length of HDR1 + LEN)
     * <p>
     * BEFORE DECODE (15 bytes)              AFTER DECODE (13 bytes)
     * +--------+------+----------------+      +------+----------------+
     * | Length | HDR1 | Actual Content |----->| HDR1 | Actual Content |
     * | 0x000C | 0xFE | protobuf bytes |      | 0xFE | protobuf bytes |
     * +--------+------+----------------+      +------+----------------+
     */
    private static final int maxFrameLength = 0xffff;
    private static final int lengthFieldOffset = 0;
    private static final int lengthFieldLength = 2;
    private static final int lengthAdjustment = 1;
    private static final int initialBytesToStrip = 2;

    private ServerBootstrap bootstrap;
    private int port;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private NettyProtobufHandler handler;

    public NettyServer(int port, NettyProtobufHandler.Callback callback) {
        this.port = port;
        bossGroup = new NioEventLoopGroup();
        workerGroup = new NioEventLoopGroup(1);

        bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup);
        handler = new NettyProtobufHandler(callback);
        bootstrap
                .option(ChannelOption.SO_BACKLOG, 1024)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000);
        bootstrap.channel(NioServerSocketChannel.class);
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel channel) throws Exception {
                channel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
//                channel.pipeline().addLast(new LineBasedFrameDecoder(33));
                channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, lengthFieldOffset,
                        lengthFieldLength, lengthAdjustment, initialBytesToStrip));
                channel.pipeline().addLast(new MessageToByteEncoder<GeneratedMessage>() {
                    @Override
                    protected void encode(ChannelHandlerContext ctx, GeneratedMessage msg, ByteBuf out) throws Exception {
                        byte[] bin = msg.toByteArray();
                        wrapSend(bin, out);
                    }
                });
                channel.pipeline().addLast(new MessageToByteEncoder<Mq2ClientMessage>() {
                    @Override
                    protected void encode(ChannelHandlerContext ctx, Mq2ClientMessage msg, ByteBuf out) throws Exception {
                        byte[] bin = msg.getBin();
                        wrapSend(bin, out);
                    }
                });
                channel.pipeline().addLast(handler);
            }
        });
    }

    private void wrapSend(byte[] bin, ByteBuf out) {
        byte lenHigh = (byte) ((bin.length >> 8) & 0xff);
        byte lenLow = (byte) (bin.length & 0xff);
        out.writeByte(lenHigh);
        out.writeByte(lenLow);
        out.writeByte(0);
        out.writeBytes(bin);
    }

    public void start() throws InterruptedException {
        bootstrap.bind(new InetSocketAddress("0.0.0.0", port)).sync();
//        bootstrap.bind("0.0.0.0", port).sync();
    }

    public void shutdown() {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }

    public CtxManager getCtxManager() {
        return handler.getCtxManager();
    }
}
